package ch.exm.storm.loader.hibernate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.proxy.HibernateProxyHelper;

import ch.exm.storm.introspection.IntrospectionUtil;
import ch.exm.storm.loader.ObjectLoadListener;
import ch.exm.storm.loader.ObjectLoader;
import ch.exm.storm.log.Logger;

public class HibernateLoader implements ObjectLoader {

	private HibernateConfiguration configuration;
	
	public HibernateLoader() {
		configuration = new HibernateConfiguration();
	}
	
	public void loadAll(ObjectLoadListener listener) {
		SessionFactory sessionFactory = configuration.getSessionFactory();
		Session session = sessionFactory.openSession();
		try {
			for (Class<?> mappedClass : configuration.getAllMappedClasses()) {
				PersistentClass persistentClass = configuration.getPersistentClass(mappedClass);
				for (Object object : loadAllByClass(mappedClass, session)) {
					loadObject(listener, persistentClass, object);
				}
			}
		} finally {
			if (session != null) session.close();
		}
	}
	
	private Object loadObject(ObjectLoadListener listener, PersistentClass persistentClass, Object object) {
		return loadObject(listener, persistentClass, object, HibernateProxyHelper.getClassWithoutInitializingProxy(object));
	}
	
	private Object loadObject(ObjectLoadListener listener, PersistentClass persistentClass, Object object, Class<?> clazz) {
		Property idProperty = persistentClass.getIdentifierProperty();
		Object id = getPropertyValue(idProperty, object);
		Object oldObject = listener.onObjectLoaded(clazz, id, object);
		if (oldObject == null) {
			inspectLoadedObject(listener, persistentClass, object);
			return object;
		}
		return oldObject;
	}
	
	private void inspectLoadedObject(ObjectLoadListener listener, PersistentClass persistentClass, Object object) {
		Iterator<?> propertyIterator = persistentClass.getPropertyIterator();
		while (propertyIterator.hasNext()) {
			Property property = (Property)propertyIterator.next();
			Object value = getPropertyValue(property, object);
			Logger.debug(this, "Inspecting property " + property.getName() + " of " + persistentClass.getClassName() + " having value " + value);
			if (value == null) continue;
			if (value instanceof List) inspectLoadedList(listener, (List<Object>)value);
			else if (value instanceof Set) inspectLoadedSet(listener, (Set<Object>)value);
			else {
				Object inspected = inspectValue(listener, value);
				if (inspected != null && inspected != value) setValueOnProperty(property, object, inspected);
			}
		}
	}
	
	private void inspectLoadedList(ObjectLoadListener listener, List<Object> list) {
		List<Object> inspectedList = new ArrayList<Object>();
		for (Object value : list) {
			Object inspected = inspectValue(listener, value);
			inspectedList.add(inspected != null && inspected != value ? inspected : null);
		}
		for (int i = 0; i < inspectedList.size(); i++) {
			Object inspectedValue = inspectedList.get(i);
			if (inspectedValue != null) list.set(i, inspectedValue);
		}
	}
	
	private void inspectLoadedSet(ObjectLoadListener listener, Set<Object> set) {
		Map<Object, Object> inspectedSet = new HashMap<Object, Object>();
		for (Object value : set) {
			Object inspected = inspectValue(listener, value);
			if (inspected != null && inspected != value) inspectedSet.put(value, inspected);
		}
		for (Object oldValue : inspectedSet.keySet()) {
			set.remove(oldValue);
			set.add(inspectedSet.get(oldValue));
		}
	}
	
	private Object inspectValue(ObjectLoadListener listener, Object value) {
		Class<?> valueClass = HibernateProxyHelper.getClassWithoutInitializingProxy(value);
		PersistentClass valuePersistentClass = configuration.getPersistentClass(valueClass);
		return valuePersistentClass == null ? null : loadObject(listener, valuePersistentClass, value, valueClass);
	}
	
	private Collection<?> loadAllByClass(Class<?> clazz, Session session) {
		Logger.debug(this, "Loading all objects of class " + clazz.getName());
		String queryString = "from " + clazz.getName();
		Query query = session.createQuery(queryString);
		query.setReadOnly(true);
		return query.list();
	}
	
	private void setValueOnProperty(Property property, Object object, Object value) {
		IntrospectionUtil.setPropertyValue(object, property.getName(), value);
	}
	
	private Object getPropertyValue(Property property, Object object) {
		return IntrospectionUtil.getPropertyValue(object, property.getName());
	}
}
